
package com.common.utils.classload;

import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

/**
 * 自定义类加载器,首先从URL加载，然后才从父级加载。
 *
 * @author xin.ding
 * @date 2021/5/21 10:06
 */
@Slf4j
public final class ChildFirstClassLoader extends URLClassLoader {

    /**
     * 定义应该始终通过父类ClassLoader进行访问的类,例如Java包含的类库
     */
    private String[] alwaysParentFirstPatterns = new String[]{
            "java.", "scala.",
            "org.apache.flink.", "com.esotericsoftware.kryo", "org.apache.hadoop.",
            "javax.annotation.",
            "org.slf4j", "org.apache.log4j", "org.apache.logging", "org.apache.commons.logging", "ch.qos.logback"
    };

    /**
     * 子类优先类加载器
     *
     * @param urls                      加载的路径
     * @param parent                    父加载器
     * @param alwaysParentFirstPatterns 使用父加载器进行加载的类
     */
    public ChildFirstClassLoader(URL[] urls, ClassLoader parent, String[] alwaysParentFirstPatterns) {
        super(urls, parent);

        if (ArrayUtil.isNotEmpty(alwaysParentFirstPatterns)) {
            this.alwaysParentFirstPatterns = ArrayUtil.addAll(this.alwaysParentFirstPatterns, alwaysParentFirstPatterns);
        }
    }


    @Override
    protected synchronized Class<?> loadClass(
            String name, boolean resolve) throws ClassNotFoundException {

        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);

        if (c == null) {
            // check whether the class should go parent-first
            for (String alwaysParentFirstPattern : alwaysParentFirstPatterns) {
                if (name.startsWith(alwaysParentFirstPattern)) {
                    return super.loadClass(name, resolve);
                }
            }

            try {
                // check the URLs
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                // let URLClassLoader do it, which will eventually call the parent
                c = super.loadClass(name, resolve);
            }
        }

        if (resolve) {
            resolveClass(c);
        }

        return c;
    }

    @Override
    public URL getResource(String name) {
        // first, try and find it via the URLClassloader
        URL urlClassLoaderResource = findResource(name);

        if (urlClassLoaderResource != null) {
            return urlClassLoaderResource;
        }

        // delegate to super
        return super.getResource(name);
    }

    @Override
    public Enumeration<URL> getResources(String name) throws IOException {
        // first get resources from URLClassloader
        Enumeration<URL> urlClassLoaderResources = findResources(name);

        final List<URL> result = new ArrayList<>();

        while (urlClassLoaderResources.hasMoreElements()) {
            result.add(urlClassLoaderResources.nextElement());
        }

        // get parent urls
        Enumeration<URL> parentResources = getParent().getResources(name);

        while (parentResources.hasMoreElements()) {
            result.add(parentResources.nextElement());
        }

        return new Enumeration<URL>() {
            Iterator<URL> iter = result.iterator();

            @Override
            public boolean hasMoreElements() {
                return iter.hasNext();
            }

            @Override
            public URL nextElement() {
                return iter.next();
            }
        };
    }
}
